package smartlifev30.com.sdk.net.baiwei;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

import smartlifev30.com.sdk.api.CallBack;
import smartlifev30.com.sdk.util.JsonUtil;


/**
 * 网络回调的基类，每次创建对象时都会向延时队列{@link BaseResponseListen#delayQueue}中放入元素。如果在指定时间内没有收到服务器应答消息，认为改条请求
 * 超时，调用{@link BaseResponseListen#onTimeOut}方法并从{@link ResponseHelper#map}中移除，不在处理该条请求。 反之，调用{@link BaseResponseListen#onFailed(String, Throwable, int)}
 * 或者{@link BaseResponseListen#onSuccess(String, String)}并从{@link BaseResponseListen#delayQueue}中移除
 *
 * @author uesr
 * @date 2018/8/7
 */

public abstract class BaseResponseListen<T> {

    /**
     * msgId 和请求一一对应关系
     */
    protected String msgId;

    /**
     * 描述信息，在用于请求超时的参数
     */
    protected String detailMessage;

    /**
     * 等待时间(毫秒)，默认{@link BaseResponseListen#DEFAULT_DELAY_TIME}
     */
    protected long delayTime;
    private TimerThread timerThread;
    public static final DelayQueue<ListenMessage> delayQueue = new DelayQueue<>();
    private static final int DEFAULT_DELAY_TIME = 8_000;
    private static volatile boolean isRunning;

    public BaseResponseListen(String msgId) {
        this(msgId, "");
    }

    public BaseResponseListen(String msgId, String detailMessage) {
        this(msgId, detailMessage, DEFAULT_DELAY_TIME);
    }

    public BaseResponseListen(String msgId, String detailMessage, int delayTime) {
        this.msgId = msgId;
        this.detailMessage = detailMessage;
        this.delayTime = delayTime;
        if (!isRunning) {
            timerThread = new TimerThread("time out thread");
            timerThread.start();
        }
        Log.e("0-0-0-", msgId + " " + detailMessage);
        ListenMessage listenMessage = new ListenMessage(msgId, detailMessage,
                System.currentTimeMillis() + delayTime);
        delayQueue.offer(listenMessage);
    }

    public String getMsgId() {
        return msgId;
    }

    class TimerThread extends Thread {
        TimerThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            try {
                isRunning = true;
                mainLoop();
            } finally {
                isRunning = false;
                delayQueue.clear();
            }
        }
    }

    private void mainLoop() {
        while (isRunning) {
            try {
                Log.e("0-0-0-", "waiting...." + Thread.currentThread().getId());
                ListenMessage take = delayQueue.take();
                onTimeOut(take.msgId, take.detailMessage);
            } catch (InterruptedException e) {
                e.printStackTrace();
                isRunning = false;
            }
        }
    }

    private static class ListenMessage implements Delayed {
        private String msgId;
        private String detailMessage;
        private long delayTime;

        ListenMessage(String msgId, String detailMessage, long delayTime) {
            this.msgId = msgId;
            this.detailMessage = detailMessage;
            this.delayTime = delayTime;
        }

        @Override
        public long getDelay(@NonNull TimeUnit unit) {
            return unit.convert(this.delayTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        @Override
        public int compareTo(@NonNull Delayed o) {
            long dif = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
            if (dif > 0) {
                return 1;
            } else if (dif == 0) {
                return 0;
            } else {
                return -1;
            }
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            ListenMessage that = (ListenMessage) o;

            return msgId.equals(that.msgId);
        }

        @Override
        public int hashCode() {
            return msgId.hashCode();
        }
    }

    /**
     * 在指定时间内{@link BaseResponseListen#DEFAULT_DELAY_TIME}没有收到服务器的应答，从延时
     * 队列中取出来的，切换到主线程
     * 超时(网络， 参数)
     *
     * @param msgId
     * @param detailMessage 描述信息,可能为""
     */
    static void onTimeOut(final String msgId, final String detailMessage) {
        BaseResponseListen<?> baseResponseListen = ResponseHelper.removeListen(msgId);
        ResponseHelper.post(() -> {
            assert baseResponseListen != null;
            baseResponseListen.onTimeOut(msgId + " _ " + detailMessage);
        });
    }

    /**
     * 提前从延时队列中移除，移除成功触发{@link #onTimeOut}.在网络不可用时将会触发
     *
     * @param msgId
     * @param detailMessage
     */
    static void removeEarly (final String msgId, final String detailMessage) {
        if (delayQueue.remove(new ListenMessage(msgId, detailMessage, 0))) {
            onTimeOut(msgId, detailMessage);
        }
    }

    protected abstract void onTimeOut(String detailMessage);

    /**
     * status非0结果
     *
     * @param msgId
     * @param throwable
     * @param code      status
     */
    void onFailed(String msgId, Throwable throwable, int code) {
        delayQueue.remove(new ListenMessage(msgId, "", 0));
        onFailed(throwable, code);
    }

    protected abstract void onFailed(Throwable throwable, int code);

    /**
     * status 为0时结果
     *
     * @param msgId
     * @param response 服务器返回的结果 json字符.
     */
    void onSuccess(String msgId, String response) {
        delayQueue.remove(new ListenMessage(msgId, "", 0));
        Class<T> type = getType(this);
        if (type != null) {
            T t = JsonUtil.toObject(response, type);
            onSuccess(t);
        } else {
            onSuccess(null);
        }
    }

    protected abstract void onSuccess(T response);

    private @Nullable
    Class<T> getType(BaseResponseListen<T> callBack) {
        Class<T> ret = null;
        Class clazz = callBack.getClass();
        while (clazz != Object.class) {
            Type superclass = clazz.getGenericSuperclass();
            if (superclass instanceof ParameterizedType) {
                Type[] args = ((ParameterizedType) superclass).getActualTypeArguments();
                if (args != null && args.length >= 1 && args[0] instanceof Class) {
                    ret = (Class<T>) args[0];
                    break;
                }
            }
        }
        return ret;
    }
}
